#include "mesh_loader_obj.h"

#include <istream>
#include <sstream>
#include <fstream>
#include <string>
#include <deque>
#include <iostream>

#include "mesh.h"

namespace krryn{
namespace scene{

class line_state_visitor;
class line_state{
public:
	virtual void parse_line(std::istream &a_Line) = 0;
	virtual void visit(line_state_visitor *a_Visitor) = 0;
	virtual ~line_state() {};
};

struct indexed_vertex{
	int v, vt, vn;
	indexed_vertex(int va, int vta, int vna) : v(va), vt(vta), vn(vna) {}
};

class line_state_vertex;
class line_state_texture_coordinate;
class line_state_vertex_normal;
class line_state_face;

class line_state_visitor{
public:
	virtual void visit_vertex(line_state_vertex *l_Vertex) = 0;
	virtual void visit_texture_coordinate(line_state_texture_coordinate *l_TextureCoordinates) = 0;
	virtual void visit_vertex_normal(line_state_vertex_normal *l_VertexNormal) = 0;
	virtual void visit_face(line_state_face *l_Face) = 0;
};

//! visit all the different line_state classes to gather their data and build a mesh object
//! the visit_* methods should fetch the data, the build_mesh method creates a mesh class
class mesh_builder_obj : public line_state_visitor{
public:
	mesh_builder_obj() : m_VertexList(0), m_IndexList(0) {}

	virtual void visit_vertex(line_state_vertex *l_Vertex);
	virtual void visit_texture_coordinate(line_state_texture_coordinate *l_TextureCoordinates);
	virtual void visit_vertex_normal(line_state_vertex_normal *l_VertexNormal);
	virtual void visit_face(line_state_face *l_Face);
	virtual scene::mesh *build_mesh();

private:
	typedef std::deque<indexed_vertex> indexed_vertex_list_t;
	typedef std::deque<size_t> index_list_t;
	typedef std::deque<math::vector3f> vertex_list_t;
	typedef vertex_list_t normal_list_t;
	typedef std::deque<std::pair<float, float> > coords_list_t;

	coords_list_t *m_Coords;
	normal_list_t *m_NormalList;
	vertex_list_t *m_VertexList;
	indexed_vertex_list_t *m_IndexedVertexList;
	index_list_t *m_IndexList;
};

class line_state_vertex : public line_state{
public:
	line_state_vertex(){
		m_Vertices = new std::deque<math::vector3f>();
	}

	virtual void parse_line(std::istream &a_Line){
		float x, y, z;
		a_Line >> x >> y >> z;
		m_Vertices->push_back(math::vector3f(x, y, z));
	};

	virtual void visit(line_state_visitor *a_Visitor){
		a_Visitor->visit_vertex(this);
	}

	std::deque<math::vector3f> *get_vertex_list() const{
		return m_Vertices;
	}

	~line_state_vertex(){
		delete m_Vertices; m_Vertices = 0;
	}

private:
	std::deque<math::vector3f> *m_Vertices;
};

class line_state_texture_coordinate : public line_state{
public:
	typedef std::deque<std::pair<float, float> > coords_list_t;

	line_state_texture_coordinate(){
		m_Coords = new coords_list_t();
	}

	virtual void parse_line(std::istream &a_Line){
		float u, v;
		a_Line >> u >> v;
		m_Coords->push_back(std::make_pair<float, float>(u, v));
	};

	virtual void visit(line_state_visitor *a_Visitor){
		a_Visitor->visit_texture_coordinate(this);
	}

	coords_list_t *get_coords_list() const{
		return m_Coords;
	}

	~line_state_texture_coordinate(){
		delete m_Coords; m_Coords = 0;
	}
private:
	coords_list_t *m_Coords;
};

class line_state_vertex_normal : public line_state{
public:
	typedef std::deque<math::vector3f> normals_list_t;

	line_state_vertex_normal(){
		m_Normals = new normals_list_t;
	}

	virtual void parse_line(std::istream &a_Line){
		float x, y, z;
		a_Line >> x >> y >> z;
		m_Normals->push_back(math::vector3f(x, y, z));
	}

	virtual void visit(line_state_visitor *a_Visitor){
		a_Visitor->visit_vertex_normal(this);
	}

	normals_list_t *get_normal_list(){
		return m_Normals;
	}

	~line_state_vertex_normal(){
		delete m_Normals; m_Normals = 0;
	}
private:
	normals_list_t *m_Normals;
};

class line_state_face : public line_state{
public:
	typedef std::deque<indexed_vertex> vertex_list_t;
	typedef std::deque<size_t> index_list_t;
	typedef std::map<std::string, size_t> duplicates_t;

	line_state_face(){
		m_VertexList = new vertex_list_t;
		m_IndexList = new index_list_t;
	}

	virtual void parse_line(std::istream &a_Line){
		std::string l_Buffer;

		while(!a_Line.eof()){
			int v = 0, vt = 0, vn = 0;

			a_Line >> l_Buffer;
			std::stringstream ss(l_Buffer);

			ss >> v;  ss.ignore(1, '/');
			ss >> vt; ss.ignore(1, '/');
			ss >> vn; ss.ignore(1, '/');

			duplicates_t::iterator i = m_DuplicateLookup.find(l_Buffer);
			if(i == m_DuplicateLookup.end()){
				// first time we find this vertex, store it and index it
				m_VertexList->push_back(indexed_vertex(v, vt, vn));
				size_t l_Position = m_VertexList->size() - 1;
				m_DuplicateLookup[l_Buffer] = l_Position;
				m_IndexList->push_back(l_Position);
			}else{
				m_IndexList->push_back(i->second);
			}
		}
	}

	vertex_list_t* get_indexed_vertex_list() const{
		return m_VertexList;
	}

	index_list_t* get_index_list() const{
		return m_IndexList;
	}

	virtual void visit(line_state_visitor *a_Visitor){
		a_Visitor->visit_face(this);
	}

	~line_state_face(){
		delete m_VertexList; m_VertexList = 0;
		delete m_IndexList; m_IndexList = 0;
	}

private:
	vertex_list_t *m_VertexList;
	index_list_t  *m_IndexList;
	duplicates_t   m_DuplicateLookup;
};

void mesh_builder_obj::visit_vertex(line_state_vertex *l_Vertex){
	m_VertexList = l_Vertex->get_vertex_list();
}

void mesh_builder_obj::visit_face(line_state_face *l_Face){
	m_IndexedVertexList = l_Face->get_indexed_vertex_list();
	m_IndexList			= l_Face->get_index_list();
}

void mesh_builder_obj::visit_texture_coordinate(line_state_texture_coordinate *a_Coord){
	m_Coords = a_Coord->get_coords_list();
}

void mesh_builder_obj::visit_vertex_normal(line_state_vertex_normal *l_Normal){
	m_NormalList = l_Normal->get_normal_list();
}

scene::mesh *mesh_builder_obj::build_mesh(){
	scene::mesh *l_Mesh = new scene::mesh();

	scene::vertex *l_VertexList = new scene::vertex[m_IndexedVertexList->size()];
	size_t *l_IndexList  = new size_t[m_IndexList->size()];

	// Yes, dear compiler, I know what I'm doing. Now shut up.
	#pragma warning(push)
	#pragma warning(disable : 4996)
	std::copy(m_IndexList->begin(), m_IndexList->end(), l_IndexList);
	#pragma warning(pop)

	size_t j = 0;
	indexed_vertex_list_t::iterator i;
	for(i = m_IndexedVertexList->begin(); i != m_IndexedVertexList->end(); i++){
		l_VertexList[j].m_Position = ((*m_VertexList)[i->v  - 1]);

		// vertex normals and texture coordinates are optional
		if(i->vn > 0){
			l_VertexList[j].m_Normal   = ((*m_NormalList)[i->vn - 1]);
		}

		if(i->vt > 0){
			std::pair<float, float> &l_UV =  ((*m_Coords)[i->vt - 1]);
			l_VertexList[j].m_U = l_UV.first;
			l_VertexList[j].m_V = l_UV.second;
		}

		j++;
	}

	l_Mesh->set_vertex_count(m_IndexedVertexList->size());
	l_Mesh->set_vertices(l_VertexList);
	l_Mesh->set_index_count(m_IndexList->size());
	l_Mesh->set_indices(l_IndexList);
	l_Mesh->update_aabb();

	return l_Mesh;
}

mesh_loader_obj::mesh_loader_obj()
{
	m_LineStates["v"]  = new line_state_vertex();
	m_LineStates["vt"] = new line_state_texture_coordinate();
	m_LineStates["vn"] = new line_state_vertex_normal();
	m_LineStates["f"]  = new line_state_face();

	m_MeshBuilder = new mesh_builder_obj();
}

mesh_loader_obj::~mesh_loader_obj(void)
{
	for(line_states_t::iterator i = m_LineStates.begin(); i != m_LineStates.end(); i++){
		line_state* l_LineState = i->second;
		delete l_LineState, l_LineState = 0;
	}

	m_LineStates.clear();
}

scene::mesh* mesh_loader_obj::load(const std::string &a_Filename){
	std::ifstream l_File;
	std::string   l_Identifier;
	std::string   l_Buffer;

	l_File.exceptions(std::ifstream::badbit | std::ifstream::failbit);

	try{
		l_File.open(a_Filename.c_str());
		l_File.exceptions(std::ifstream::badbit);

		for(size_t i = 1; !l_File.eof(); i++){
			l_File >> l_Identifier;

			// read the rest of the line and
			// pass it as a stringstream to
			// the correct line_state
			std::getline(l_File, l_Buffer);
			std::istringstream l_Line(l_Buffer);

			l_Line.exceptions(std::istringstream::badbit | std::istringstream::failbit);

			// ignore the the complete line if 
			// the identifier is not found in
			// the m_LineStates map
			if(m_LineStates.find(l_Identifier) != m_LineStates.end()){
				try{
					m_LineStates[l_Identifier]->parse_line(l_Line);
				}catch(std::istringstream::failure &e){
					std::clog << "Parse error '" << e.what() << "' on line " << i << std::endl;
				}
			}
		}

		l_File.close();
	}catch(std::ifstream::failure &e){
		std::clog << "Failed to open file '" << a_Filename << "', error: '" << e.what() << "'" << std::endl;
		return 0;
	}

	l_File.close();

	for(line_states_t::iterator i = m_LineStates.begin(); i != m_LineStates.end(); i++){
		i->second->visit(m_MeshBuilder);
	}

	return m_MeshBuilder->build_mesh();
}
}
}
